<pre>

<script>

class XRegExp {
  constructor(source, flag, root = 'root') {
    this.table = new Map();
    this.regexp = new RegExp(this.compileRegExp(source, root, 0).source, flag);
    console.log(this.regexp);
    console.log(this.table);
  }
  compileRegExp(source, name, start) {
    if (source[name] instanceof RegExp) {
      return {
        source: source[name].source,
        length: 0
      };
    }

    let length = 0;

    let regexp = source[name].replace(/\<([^>]+)\>/g, (str, $1) => {
      this.table.set(start + length, $1);
      // this.table.set($1, start + length);

      ++length;

      let r = this.compileRegExp(source, $1, start + length);

      length += r.length;
      return "(" + r.source + ")";
    });

    return {
      source: regexp,
      length: length
    }
  }
  exec(string) {
    let r = this.regexp.exec(string);

    for (let i = 1; i < r.length; i++) {
      if (r[i] !== void 0) {
        // console.log(this.table.get(i-1));
        r[this.table.get(i - 1)] = r[i];
      }
    }

    console.log(JSON.stringify(r[0]));
    return r;
  }
  get lastIndex() {
    return this.regexp.lastIndex;
  }
  set lastIndex(value) {
    this.regexp.lastIndex = value;
  }
}

function scan(str) {
  let regexp = new XRegExp({
    InputElement: "<WhiteSpace>|<LineTerminator>|<Comments>|<Token>",
    WhiteSpace: / /,
    LineTerminator: /\n/,
    Comments: /\/\*(?:[^*]|\*[^\/])*\*\/|\/\/[^\n]*/,
    Token: "<Literal>|<Keywords>|<Identifier>|<Punctuator>",
    Literal: "<NumericLiteral>|<BooleanLiteral>|<StringLiteral>|<NullLiteral>",
    NumericLiteral: /(?:[1-9][0-9]*|0)(?:\.[0-9]*)?|\.[0-9]+/,
    BooleanLiteral: /true|false/,
    StringLiteral: /\"(?:[^"\n]|\\[\s\S])*\"|\'(?:[^'\n]|\\[\s\S])*\'/,
    NullLiteral: /null/,
    Identifier: /[a-zA-Z_$][a-zA-Z0-9_$]*/,
    Keywords: /if|else|for|function|let/,
    Punctuator: /\,|\+|\?|\:|\{|\}|\(|=|<|\+\+|==|=>|\*|\.|\)|\[|\]|;/,
  }, 'g', "InputElement");

  while(regexp.lastIndex < str.length) {
    let r = regexp.exec(str);

    console.log(r);

    if (!r[0].length)
      break;
    // console.log(JSON.stringify(r[0]));

    // document.write(r[0]);
  }
}

scan(`
  for (let i = 0; i < 3; i++) {
    for (let j = 0; j < 3; j++) {
      let cell = document.createElement("div");
      cell.classList.add("cell");
      cell.innerText = pattern[i * 3 + j] == 2 ? "❌" ;
      pattern[i * 3 + j] == 1 ? "⭕️" : "";
      cell.addEventListener("click", () => userMove(j, i));
      board.appendChild(cell);
    }
    board.appendChild(document.createElement("br"));
  }
`)

</script>

</pre>
